// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package ld

import (
	"cmd/internal/sys"
	"cmd/link/internal/loader"
	"encoding/binary"
	"errors"
	"log"
	"os"
)

// 如果此平台不支持fallocate，则返回此错误。
// 错误在需要时被忽略，EXPUFF会写入堆内存。
var errNoFallocate = errors.New("operation not supported")

const outbufMode = 0775

// exputf是一个缓冲文件编写器。
// 
// 对于cmd/internal/bio中的作者来说，这很相似，但有一些小的差异。
// 
// 首先，它跟踪输出架构，并使用它提供
// endian助手。第二，它提供了一个非常便宜的偏移计数器，不需要任何系统调用来读取值。
// 
// 第三，它还映射输出文件（如果可用）。预期用途是：
// /-Mmap输出文件
// /-写入内容
// /-可能在输出缓冲区中应用任何编辑
// /-可能向文件写入更多内容。这些写操作发生在一个堆
// 备份的缓冲区中，该缓冲区将同步到磁盘。
// Munmap输出文件
// 
// 最后，它提供了一种机制，通过这种机制，您可以多线程地写入输出文件。这个机制是通过复制一个extuf，
// 并在线程/goroutine中使用它来实现的。
// 
// Parallel exputf的用法如下：
// 
// func write（out*exputf）{
// var wg sync.WaitGroup 
// for i:=0；i<10；i++{
// wg.Add 1）
// view，err out.view（start[i]）{
// /处理输出
// /继续
// }
// go func（out*expuf，i int）{
// /做输出
// /wg.Done（）
// }（view，i）
// }。Wait（）
// }
type OutBuf struct {
	arch *sys.Arch
	off  int64

	buf  []byte // mmap输出文件的备份存储
	heap []byte // 非mmap数据的备份存储

	name   string
	f      *os.File
	encbuf [8]byte // 写入方法使用的临时缓冲区
	isView bool    // 如果从视图创建，则为true（）
}

func (out *OutBuf) Open(name string) error {
	if out.f != nil {
		return errors.New("cannot open more than one file")
	}
	f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, outbufMode)
	if err != nil {
		return err
	}
	out.off = 0
	out.name = name
	out.f = f
	return nil
}

func NewOutBuf(arch *sys.Arch) *OutBuf {
	return &OutBuf{
		arch: arch,
	}
}

var viewError = errors.New("output not mmapped")

func (out *OutBuf) View(start uint64) (*OutBuf, error) {
	return &OutBuf{
		arch:   out.arch,
		name:   out.name,
		buf:    out.buf,
		heap:   out.heap,
		off:    int64(start),
		isView: true,
	}, nil
}

var viewCloseError = errors.New("cannot Close OutBuf from View")

func (out *OutBuf) Close() error {
	if out.isView {
		return viewCloseError
	}
	if out.isMmapped() {
		out.copyHeap()
		out.purgeSignatureCache()
		out.munmap()
	}
	if out.f == nil {
		return nil
	}
	if len(out.heap) != 0 {
		if _, err := out.f.Write(out.heap); err != nil {
			return err
		}
	}
	if err := out.f.Close(); err != nil {
		return err
	}
	out.f = nil
	return nil
}

// 错误关闭输出文件（如果有）。
// 应该只在出错时退出时调用它，所以它不进行任何清理或缓冲区刷新，只关闭文件。
func (out *OutBuf) ErrorClose() {
	if out.isView {
		panic(viewCloseError)
	}
	if out.f == nil {
		return
	}
	out.f.Close() // 尽力而为，忽略错误
	out.f = nil
}

// isMmapped如果触发，则返回true。
func (out *OutBuf) isMmapped() bool {
	return len(out.buf) != 0
}

// 数据以字节片的形式返回整个写入的EXBUFF。
func (out *OutBuf) Data() []byte {
	if out.isMmapped() {
		out.copyHeap()
		return out.buf
	}
	return out.heap
}

// copyHeap将堆复制到内存的mmapped部分，如果发生复制，则返回true。
func (out *OutBuf) copyHeap() bool {
	if !out.isMmapped() { // 仅对MMAPED EXPUFF有价值。
		return false
	}
	if out.isView {
		panic("can't copyHeap a view")
	}

	bufLen := len(out.buf)
	heapLen := len(out.heap)
	total := uint64(bufLen + heapLen)
	if heapLen != 0 {
		if err := out.Mmap(total); err != nil { // Mmap将复制。堆到外面去。buf 
			Exitf("mapping output file failed: %v", err)
		}
	}
	return true
}

// maxburghfheaplen限制堆区域的增长。
const maxOutBufHeapLen = 10 << 20

// writeLoc确定缓冲区是否已满时的写入位置。
// 我们为
// 写入维护了两个写缓冲区、一个mmapped部分和一个heap部分。当MMMapped部分已满时，我们切换堆内存
// 进行写入。
func (out *OutBuf) writeLoc(lenToWrite int64) (int64, []byte) {
	// 看看我们在mmaped区域是否有足够的空间。
	bufLen := int64(len(out.buf))
	if out.off+lenToWrite <= bufLen {
		return out.off, out.buf
	}

	// mmaped区域空间不足，请改为写入堆区域。
	heapPos := out.off - bufLen
	heapLen := int64(len(out.heap))
	lenNeeded := heapPos + lenToWrite
	if lenNeeded > heapLen { // 我们需要增加堆存储吗？
		// 堆变量不受互斥锁保护。现在，如果你想同时使用EXBUFF，那就炸掉它吧。（请注意，这可能是可以解决的。）
		if out.isView {
			panic("cannot write to heap in parallel")
		}
		// 看看我们的堆是否会变得太大，如果会，请将其复制到mmapped区域的末尾
		// 处。
		if heapLen > maxOutBufHeapLen && out.copyHeap() {
			heapPos -= heapLen
			lenNeeded = heapPos + lenToWrite
			heapLen = 0
		}
		out.heap = append(out.heap, make([]byte, lenNeeded-heapLen)...)
	}
	return heapPos, out.heap
}

func (out *OutBuf) SeekSet(p int64) {
	out.off = p
}

func (out *OutBuf) Offset() int64 {
	return out.off
}

// Write将v的内容写入缓冲区。
func (out *OutBuf) Write(v []byte) (int, error) {
	n := len(v)
	pos, buf := out.writeLoc(int64(n))
	copy(buf[pos:], v)
	out.off += int64(n)
	return n, nil
}

func (out *OutBuf) Write8(v uint8) {
	pos, buf := out.writeLoc(1)
	buf[pos] = v
	out.off++
}

// WriteByte是Write8实现io的别名。字节编写器接口。
func (out *OutBuf) WriteByte(v byte) error {
	out.Write8(v)
	return nil
}

func (out *OutBuf) Write16(v uint16) {
	out.arch.ByteOrder.PutUint16(out.encbuf[:], v)
	out.Write(out.encbuf[:2])
}

func (out *OutBuf) Write32(v uint32) {
	out.arch.ByteOrder.PutUint32(out.encbuf[:], v)
	out.Write(out.encbuf[:4])
}

func (out *OutBuf) Write32b(v uint32) {
	binary.BigEndian.PutUint32(out.encbuf[:], v)
	out.Write(out.encbuf[:4])
}

func (out *OutBuf) Write64(v uint64) {
	out.arch.ByteOrder.PutUint64(out.encbuf[:], v)
	out.Write(out.encbuf[:8])
}

func (out *OutBuf) Write64b(v uint64) {
	binary.BigEndian.PutUint64(out.encbuf[:], v)
	out.Write(out.encbuf[:8])
}

func (out *OutBuf) WriteString(s string) {
	pos, buf := out.writeLoc(int64(len(s)))
	n := copy(buf[pos:], s)
	if n != len(s) {
		log.Fatalf("WriteString truncated. buffer size: %d, offset: %d, len(s)=%d", len(out.buf), out.off, len(s))
	}
	out.off += int64(n)
}

// WriteStringN写入s的前n个字节。
// 如果n大于len，则用零字节填充。
func (out *OutBuf) WriteStringN(s string, n int) {
	out.WriteStringPad(s, n, zeros[:])
}

// WriteStringPad写入s的前n个字节。
// 如果n大于len，则用pad中的字节填充（根据需要重复）。
func (out *OutBuf) WriteStringPad(s string, n int, pad []byte) {
	if len(s) >= n {
		out.WriteString(s[:n])
	} else {
		out.WriteString(s)
		n -= len(s)
		for n > len(pad) {
			out.Write(pad)
			n -= len(pad)

		}
		out.Write(pad[:n])
	}
}

// WriteSym写入一个符号的内容，并返回我们刚刚编写的输出缓冲区
// 以便对符号内容进行进一步编辑。
// 对于生成器符号，它还将符号的数据设置为输出
// 缓冲区。
func (out *OutBuf) WriteSym(ldr *loader.Loader, s loader.Sym) []byte {
	if !ldr.IsGeneratedSym(s) {
		P := ldr.Data(s)
		n := int64(len(P))
		pos, buf := out.writeLoc(n)
		copy(buf[pos:], P)
		out.off += n
		ldr.FreeData(s)
		return buf[pos : pos+n]
	} else {
		n := ldr.SymSize(s)
		pos, buf := out.writeLoc(n)
		out.off += n
		ldr.MakeSymbolUpdater(s).SetData(buf[pos : pos+n])
		return buf[pos : pos+n]
	}
}
